﻿using System;
using System.IO;
using GACore.Progress;
using GACore.ReversiGA;

namespace GACore
{
	public class Controller
	{
		private Preferences preferences;
		private Problem problem;

		private int currentOptimizationCount;
		public int CurrentOptimizationCount
		{
			get { return currentOptimizationCount; }
			set { currentOptimizationCount = value; }
		}

		private History history = new History();
		private string directoryName;

		private bool isAborted = false;

		#region Singleton
		private static Controller instance = new Controller();
		public static Controller Instance
		{
			get { return Controller.instance; }
			set { Controller.instance = value; }
		}

		private Controller() { }

		#endregion

		public void Init(Preferences preferences, Problem problem)
		{
			this.preferences = preferences;
			this.problem = problem;
			this.CurrentOptimizationCount = 0;
			this.history = new History();
			this.isAborted = false;
			history.AddEntry(problem.Init(preferences));
			InitHistory();
		}

		private void InitHistory()
		{
			this.directoryName = problem.GetProblemName() + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
			Directory.CreateDirectory(directoryName);
			SaveHistoryEntry();
		}

		public OptimizationFinishedArgs Optimize()
		{
			while (NotAbort())
			{
				history.AddEntry(problem.ComputeStep());
				SaveHistoryEntry();
				OnStepComputed(history.Current);
			}
			return FinishOptimization();
		}

		public void CancelOptimization()
		{
			isAborted = true;
			FinishOptimization();
		}

		private OptimizationFinishedArgs FinishOptimization()
		{
			string cout = isAborted ? "Die Optimierung wurde abgebrochen. " : "Es wurde ein Optimum gefunden. ";
			cout += "Der Optimierungsverlauf kann im Ordner " + directoryName + " eingesehen werden.";
			Console.WriteLine(cout + Environment.NewLine + "Beste Lösung hat Fitness:" + history.Current.BestVector.Fitness);
			File.WriteAllText(directoryName + "/" + problem.GetProblemName() + ", Beste Lösung.txt",
				history.Current.BestVector.ToString() + Environment.NewLine + "Fitness der besten Lösung: " + history.Current.BestVector.Fitness);
			string filenamePrefs = directoryName + "/" + "GAPreferences.xml";
			new InputArgs.Persistence().Persist(filenamePrefs, preferences);
			return GetOptimizationFinishedEventArgs(cout);
		}

		private OptimizationFinishedArgs GetOptimizationFinishedEventArgs(string resultString)
		{
			OptimizationFinishedArgs args = new OptimizationFinishedArgs();
			args.ResultString = resultString;
			args.BestResult = ((Vector<double>)history.Current.BestVector).Elements;
			args.Directory = directoryName;
			return args;

		}

		private bool NotAbort()
		{
			if (isAborted || CurrentOptimizationCount++ >= preferences.OptimizationSteps)
			{
				return false;
			}
			for (int i = history.Current.Step; i >= 0 && i >= history.Current.Step - 20; i--)
			{
				for (int j = 0; j < history[i].Population.Length; j++)
				{
					if (history.Current.Step < 20 || history[i].Population[j].Fitness != history.Current.Population[j].Fitness)
					{
						return true;
					}
				}
			}
			return false;
		}

		private void SaveHistoryEntry()
		{
			Console.WriteLine(history.Current.ToShortString());
			if (preferences.IsVerbose)
			{
				string filename = directoryName + "/" + problem.GetProblemName() + ", Schritt " + history.Current.Step + ".txt";
				string content = history.Current.ToLongString() + Environment.NewLine + Environment.NewLine + problem.GetProblemRepresentation();
				File.WriteAllText(filename, content);
			}
		}

		#region Events
		#region StepComputed
		public delegate void StepComputedEventHandler(object sender, StepComputedEventArgs e);

		public event StepComputedEventHandler StepComputed;

		protected void OnStepComputed(HistoryEntry currentHistoryEntry)
		{
			if (StepComputed != null)
			{
				StepComputedEventArgs args = new StepComputedEventArgs();
				args.CurrentHistoryEntry = currentHistoryEntry;
				StepComputed(this, args);
			}
		}
		#endregion
		#endregion

	}
}
